package com.ybb.seckilldemo.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wf.captcha.ArithmeticCaptcha;
import com.ybb.seckilldemo.exception.GlobalException;
import com.ybb.seckilldemo.pojo.Order;
import com.ybb.seckilldemo.pojo.SeckillMessage;
import com.ybb.seckilldemo.pojo.SeckillOrder;
import com.ybb.seckilldemo.pojo.User;
import com.ybb.seckilldemo.rabbitmq.MQSender;
import com.ybb.seckilldemo.service.Impl.GoodsServiceImpl;
import com.ybb.seckilldemo.service.Impl.OrderServiceImpl;
import com.ybb.seckilldemo.service.Impl.SeckillOrderServiceImpl;
import com.ybb.seckilldemo.utils.JsonUtil;
import com.ybb.seckilldemo.vo.GoodsVo;
import com.ybb.seckilldemo.vo.RespBean;
import com.ybb.seckilldemo.vo.RespBeanEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 秒杀
 *
 * 优化前windows QPS：330.9
 * mybatis QPS：208.1
 * mybatis plus QPS：194.4
 */

@Slf4j
@Controller
@RequestMapping("/seckill")
@CrossOrigin
public class SeckillController implements InitializingBean {

    @Autowired
    private GoodsServiceImpl goodsService;

    @Autowired
    private SeckillOrderServiceImpl seckillOrderService;

    @Autowired
    private OrderServiceImpl orderService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private MQSender mqSender;

    // 内存标记
    private Map<Long, Boolean> EmptyStock = new HashMap<>();

    @Autowired
    private RedisScript<Long> redisScript;

    /**
     * 前后端分离
     * @param
     * @param user
     * @param goodsId
     * @return
     */
    @RequestMapping(value = "/{path}/doSeckill",method = RequestMethod.POST)
    @ResponseBody
    public RespBean doSeckill(@PathVariable String path, User user, Long goodsId) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.ERROR);
        }
        // 通过内存标记，减少Redis的访问
        if (EmptyStock.get(goodsId)) {
            return RespBean.error(RespBeanEnum.EMPT_STOCK);
        }

        ValueOperations valueOperations = redisTemplate.opsForValue();

        // 判断请求路径
        boolean check = orderService.checkPath(user, goodsId,path);
        if (!check) {
            return RespBean.error(RespBeanEnum.ERROR);
        }

        // 判断是否重复提交  不然redis库存会被减完 但是实际库存还有

        // 判断 是否重复抢购
        SeckillOrder seckillOrder  =
                (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);
        if (seckillOrder != null) {
            return RespBean.error(RespBeanEnum.REPEATE_ERROR);
        }

        // 预减库存
//        Long stock = valueOperations.decrement("seckillGoods:" + goodsId);
//         利用lua脚本
        Long stock = (Long) redisTemplate.execute(redisScript, Arrays.asList("seckillGoods:" + goodsId), Collections.EMPTY_LIST);
        if (stock < 0) {
            EmptyStock.put(goodsId, true);
            valueOperations.increment("seckillGoods:" + goodsId);
            return RespBean.error(RespBeanEnum.EMPT_STOCK);
        }
        // 下单
        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
        mqSender.sendSeckillMessage(JsonUtil.object2JsonStr(seckillMessage));
        return RespBean.success(0);

        /*
        GoodsVo goods = goodsService.findGoodsVoByGoodsId(goodsId);
        // 判断库存
        if (goods.getStockCount() < 1) {
            model.addAttribute("errmsg", RespBeanEnum.EMPT_STOCK.getMessage());
            return RespBean.error(RespBeanEnum.EMPT_STOCK);
        }
        // 判断 是否重复抢购
//        SeckillOrder seckillOrder = seckillOrderService.getOne(new QueryWrapper<SeckillOrder>().eq("user_id", user.getId())
//                .eq("goods_id", goodsId));
        SeckillOrder seckillOrder  =
                (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goods.getId());
        if (seckillOrder != null) {
            return RespBean.error(RespBeanEnum.REPEATE_ERROR);
        }
        Order order = orderService.seckill(user, goods);
        return RespBean.success(order);*/

    }

    /**
     * 不是前后端分离
     * @param model
     * @param user
     * @param goodsId
     * @return
     */
    @RequestMapping("/doSeckill2")
    public String doSeckill2(Model model, User user, Long goodsId) {
        if (user == null) {
            return "login";
        }
        model.addAttribute("user", user);
        GoodsVo goods = goodsService.findGoodsVoByGoodsId(goodsId);
        // 判断库存
        if (goods.getStockCount() < 1) {
            model.addAttribute("errmsg", RespBeanEnum.EMPT_STOCK.getMessage());
            return "secKillFail";
        }
        // 判断 是否重复抢购
        SeckillOrder seckillOrder = seckillOrderService.getOne(new QueryWrapper<SeckillOrder>().eq("user_id", user.getId())
                .eq("goods_id", goodsId));
        if (seckillOrder != null) {
            model.addAttribute("errmsg", RespBeanEnum.REPEATE_ERROR.getMessage());
            return "secKillFail";
        }
        Order order = orderService.seckill(user, goods);
        model.addAttribute("order", order);
        model.addAttribute("goods", goods);
        return "orderDetail";
    }


    /**
     * 获取秒杀结果
     * @param user
     * @param goodsId
     * @return orderId：成功，  -1，秒杀失败， 0，排队中
     */
    @RequestMapping(value = "/result", method = RequestMethod.POST)
    @ResponseBody
    public RespBean getResult(User user,Long goodsId) {

        if (user == null) {
            return RespBean.error(RespBeanEnum.ERROR);
        }
        Long orderId = seckillOrderService.getResult(user, goodsId);
        return RespBean.success(orderId);
    }

    /**
     * 获取秒杀地址
     */
    @RequestMapping(value = "/path", method = RequestMethod.POST)
    @ResponseBody
    public RespBean getPath(User user, Long goodsId, String captcha, HttpServletRequest request) {
        if (null == user) {
            return RespBean.error(RespBeanEnum.ERROR);
        }
        ValueOperations valueOperations = redisTemplate.opsForValue();

        // 限制访问次数  5秒秒内访问5次
        String uri = request.getRequestURI();
        Integer count = (Integer) valueOperations.get(uri + ":" + user.getId());
        if (count == null) {
            valueOperations.set(uri + ":" + user.getId(),1,5,TimeUnit.SECONDS);
        }
        if (count < 5) {
            valueOperations.increment(uri + ":" + user.getId());
        } else {
            return RespBean.error(RespBeanEnum.ACCESS_LIMIT_REAHCED);
        }
        // 验证 验证码
        boolean check = orderService.checkCaptcha(user, goodsId, captcha);
        if (!check) {
            return RespBean.error(RespBeanEnum.ERROR_CAPTCHA);
        }
        String str = orderService.createPath(user, goodsId);
        return RespBean.success(str);
    }

    /**
     * 验证码
     */
    @RequestMapping(value = "/captcha", method = RequestMethod.GET)
    @ResponseBody
    public RespBean verifyCode(User user, Long goodsId, HttpServletResponse response) {
        if (user == null) {
            throw new GlobalException(RespBeanEnum.ERROR);
        }
//        设置请求头为输出图片的类型
        response.setContentType("image/jpg");
        response.setHeader("Pargam", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expries", 0);
//        生成验证码  放在redis
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(130, 32, 3);
        redisTemplate.opsForValue().set("captcha" + user.getId() + ":" + goodsId, captcha.text(), 300, TimeUnit.SECONDS);
        //            captcha.out(response.getOutputStream());
        return RespBean.success(captcha.toBase64());
    }


    /**
     * 系统初始化，把商品库存数量加载到Redis
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> list = goodsService.findGoodsVo();
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        list.forEach(goodsVo -> {
            redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());
            EmptyStock.put(goodsVo.getId(), false);
        });
    }
}
